﻿#region using statements

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;

using NGen.Managers.Logging;

#endregion

namespace NGen.Managers.GameState
{

	#region using declarations

	#endregion

	/// <summary>
	/// 	Manage the registration, switching, updating and transitioning between 
	/// 	game states
	/// </summary>
	public sealed class ManagerGameState : GameManager, IManagerGameState
	{
		#region local fields

		/// <summary>
		/// 	the state stack
		/// </summary>
		private readonly Stack<GameState> m_sStateStack = new Stack<GameState>();

		/// <summary>
		/// 	event which allows objects to listen for game state changes
		/// </summary>
		private event GameStateChangedEventHandler ThrowGameStateChangedEvent;

		#endregion

		#region constructors

		public ManagerGameState() {
			CurrentLoggingLevel = LoggingLevel.MINIMAL;
			Logger = GameEngine.GetService<IManagerLogs>().Log<ManagerGameState>();
			SkipUpdate = false;
			SkipDraw = false;
			UpdateOrder = 100;
			DrawOrder = 100;
		}

		#endregion

		#region GameManager implementation

		/// <summary>
		/// 	_Initialize the state manager
		/// </summary>
		internal override void Initialize() {
			if(StartingState == null) {
				throw new Exception("Starting state was not set in the engine");
			}
			ChangeState(StartingState, true);
		}

		/// <summary>
		/// 	Update the current GameStateObject
		/// </summary>
		/// <param name = "p_gameTime">a snapshot of timing values</param>
		internal override void Update(GameTime p_gameTime) {
			if(m_sStateStack.Count > 0) {
				m_sStateStack.Peek()._Update(p_gameTime);
			}
		}

		/// <summary>
		/// 	Draw the current GameStateObject
		/// </summary>
		/// <param name = "p_gameTime">a snapshot of timing values</param>
		internal override void Draw(GameTime p_gameTime) {
			if(m_sStateStack.Count > 0) {
				m_sStateStack.Peek()._Draw(p_gameTime);
			}
		}

		/// <summary>
		/// 	Cleanup all states on the stack
		/// </summary>
		internal override void Cleanup() {
			PopState(true);
		}

		#endregion

		#region IManagerGameState implementation

		/// <summary>
		/// 	Add or remove a listener to be notified of game state changes
		/// </summary>
		event GameStateChangedEventHandler IManagerGameState.GameStateChangedEvent {
			add { ThrowGameStateChangedEvent += value; }
			remove { ThrowGameStateChangedEvent -= value; }
		}

		/// <summary>
		/// 	Returns the top state name, if there is a state on the stack. Empty
		/// 	string if no state on the stack
		/// </summary>
		public string CurrentStateName {
			get { return m_sStateStack.Count > 0 ? m_sStateStack.Peek().StateName : ""; }
		}

		/// <summary>
		/// 	Get or set the default starting state
		/// </summary>
		public GameState StartingState { get; set; }

		/// <summary>
		/// 	Push a new state onto the stack
		/// </summary>
		/// <param name = "p_newState">name of the state to push</param>
		public void PushState(GameState p_newState) {
			//notify top state it is losing focus
			if(m_sStateStack.Count > 0) {
				m_sStateStack.Peek()._OnFocusLost();
			}

			//push the new state onto the stack, intialize it
			m_sStateStack.Push(p_newState);
			p_newState._Initialize();
			p_newState._OnFocusGained();

			//notify listeners the state has changes
			if(ThrowGameStateChangedEvent != null) {
				ThrowGameStateChangedEvent(p_newState.StateName);
			}

			Logger.Write(Logger.BuildString(GetType().Name).Append(": state pushed -> ").Append(p_newState.GetType().Name).ToString(), CurrentLoggingLevel);
		}

		/// <summary>
		/// 	<para>Pop the top state from the stack.</para>
		/// 	<para>Does nothing if there are no states on the stack</para>
		/// </summary>
		public void PopState(bool p_clearStack = false) {
			//notify top state it is losing focus
			if(m_sStateStack.Count > 0) {
				m_sStateStack.Peek()._OnFocusLost();
			}

			if(p_clearStack) { //clear the stack cleaning up each state on it
				while(m_sStateStack.Count > 0) {
					Logger.Write(Logger.BuildString(GetType().Name).Append(": state popped -> ").Append(m_sStateStack.Peek().GetType().Name).ToString(), CurrentLoggingLevel);
					m_sStateStack.Pop()._Cleanup();
				}
			} else
				//else just pop the top state and clean it up
				if(m_sStateStack.Count > 0) {
					Logger.Write(Logger.BuildString(GetType().Name).Append(": state popped -> ").Append(m_sStateStack.Peek().GetType().Name).ToString(), CurrentLoggingLevel);
					m_sStateStack.Pop()._Cleanup();
				}

			//resume the top state, if there is one
			if(m_sStateStack.Count > 0) {
				m_sStateStack.Peek()._OnFocusGained();

				//notify listeners the state has changed
				if(ThrowGameStateChangedEvent != null) {
					ThrowGameStateChangedEvent(m_sStateStack.Peek().StateName);
				}

				Logger.Write(Logger.BuildString(GetType().Name).Append(": new state -> ").Append(m_sStateStack.Peek().GetType().Name).ToString(), CurrentLoggingLevel);
			}
		}

		/// <summary>
		/// 	Called by StateObjects to change the engine to a new state
		/// </summary>
		/// <param name = "p_newState">string value of the new state</param>
		/// <param name = "p_clearStack">clear the stack</param>
		public void ChangeState(GameState p_newState, bool p_clearStack = false) {
			//notify top state it is losing focus
			if(m_sStateStack.Count > 0) {
				m_sStateStack.Peek()._OnFocusLost();
			}

			if(p_clearStack) { //clear the stack cleaning up each state on it
				while(m_sStateStack.Count > 0) {
					Logger.Write(Logger.BuildString(GetType().Name).Append(": state popped -> ").Append(m_sStateStack.Peek().GetType().Name).ToString(), CurrentLoggingLevel);
					m_sStateStack.Pop()._Cleanup();
				}
			} else
				//else just pop the top state and clean it up
				if(m_sStateStack.Count > 0) {
					Logger.Write(Logger.BuildString(GetType().Name).Append(": state popped -> ").Append(m_sStateStack.Peek().GetType().Name).ToString(), CurrentLoggingLevel);
					m_sStateStack.Pop()._Cleanup();
				}

			//push the new state onto the stack, then initialize it
			m_sStateStack.Push(p_newState);
			p_newState._Initialize();
			p_newState._OnFocusGained();

			//notify listeners that the state has changed
			if(ThrowGameStateChangedEvent != null) {
				ThrowGameStateChangedEvent(p_newState.StateName);
			}

			Logger.Write(Logger.BuildString(GetType().Name).Append(": state changed -> ").Append(p_newState.GetType().Name).ToString(), CurrentLoggingLevel);
		}

		#endregion
	}
}